use regex::Regex;
use std::collections::HashSet;
use std::sync::LazyLock;

static LICENSES: LazyLock<HashSet<&str>> = LazyLock::new(|| LICENSE_LIST.iter().copied().collect());
// License IDs are `idstring = 1*(ALPHA / DIGIT / "-" / "." )` (https://spdx.github.io/spdx-spec/v3.0.1/annexes/spdx-license-expressions/)
static LICENSE_ID_REGEX: LazyLock<Regex> =
    LazyLock::new(|| Regex::new("[A-Za-z0-9.-]+").expect("Known regex must compile"));

/// A segment of an SPDX license string
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum LicenseSegment {
    /// This is a set of glue tokens like OR, AND, `/`, `(`, `)`, etc.
    ///
    /// Future improvement possibility: format glue tokens differently
    GlueTokens(String),
    /// This is a license that is found on the SPDX website
    Spdx(String),
    /// This is a license that is not found on the SPDX website
    UnknownLicense(String),
}

// Code similar to https://github.com/rust-lang/crates.io/blob/ed13e9f3ea3e84b7baa39d63fb8d2f7056eec3a1/app/utils/license.js#L752-L783
pub fn parse_license(license: &str) -> Vec<LicenseSegment> {
    let mut last_end = 0;
    let mut segments = Vec::new();
    for mat in LICENSE_ID_REGEX.find_iter(license) {
        let single_license = mat.as_str();
        if single_license.eq_ignore_ascii_case("and")
            || single_license.eq_ignore_ascii_case("or")
            || single_license.eq_ignore_ascii_case("with")
        {
            // not a license, these are glue tokens
            continue;
        }
        if mat.range().start != last_end {
            segments.push(LicenseSegment::GlueTokens(
                license[last_end..mat.range().start].into(),
            ));
        }
        last_end = mat.range().end;
        let single_license = single_license.to_string();
        if LICENSES.contains(&*single_license) {
            segments.push(LicenseSegment::Spdx(single_license))
        } else {
            segments.push(LicenseSegment::UnknownLicense(single_license))
        }
    }
    if last_end != license.len() {
        segments.push(LicenseSegment::GlueTokens(
            license[last_end..license.len()].into(),
        ));
    }
    segments
}

#[test]
fn test_parse_license() {
    fn glue(x: &str) -> LicenseSegment {
        LicenseSegment::GlueTokens(x.into())
    }
    fn spdx(x: &str) -> LicenseSegment {
        LicenseSegment::Spdx(x.into())
    }
    fn unknown(x: &str) -> LicenseSegment {
        LicenseSegment::UnknownLicense(x.into())
    }
    assert_eq!(
        parse_license("  Apache-2.0   "),
        vec![glue("  "), spdx("Apache-2.0"), glue("   ")]
    );
    assert_eq!(
        parse_license("Apache-2.0/MIT"),
        vec![spdx("Apache-2.0"), glue("/"), spdx("MIT")]
    );
    assert_eq!(
        parse_license("(Apache-2.0/MIT) AND Unicode-3.0"),
        vec![
            glue("("),
            spdx("Apache-2.0"),
            glue("/"),
            spdx("MIT"),
            glue(") AND "),
            spdx("Unicode-3.0")
        ]
    );
    assert_eq!(
        parse_license("Ferris-Totally-Legal-License-3.5"),
        vec![unknown("Ferris-Totally-Legal-License-3.5")]
    );
}

// Same list as that in crates.io: https://github.com/rust-lang/crates.io/blob/ed13e9f3ea3e84b7baa39d63fb8d2f7056eec3a1/app/utils/license.js#L49-L50
// from https://raw.githubusercontent.com/spdx/license-list-data/refs/heads/main/json/licenses.json
// <licenses.json jq '.licenses[] | select(.isDeprecatedLicenseId | not).licenseId' | sort
static LICENSE_LIST: &[&str] = &[
    "0BSD",
    "3D-Slicer-1.0",
    "AAL",
    "Abstyles",
    "AdaCore-doc",
    "Adobe-2006",
    "Adobe-Display-PostScript",
    "Adobe-Glyph",
    "Adobe-Utopia",
    "ADSL",
    "AFL-1.1",
    "AFL-1.2",
    "AFL-2.0",
    "AFL-2.1",
    "AFL-3.0",
    "Afmparse",
    "AGPL-1.0-only",
    "AGPL-1.0-or-later",
    "AGPL-3.0-only",
    "AGPL-3.0-or-later",
    "Aladdin",
    "AMD-newlib",
    "AMDPLPA",
    "AML",
    "AML-glslang",
    "AMPAS",
    "ANTLR-PD",
    "ANTLR-PD-fallback",
    "any-OSI",
    "any-OSI-perl-modules",
    "Apache-1.0",
    "Apache-1.1",
    "Apache-2.0",
    "APAFML",
    "APL-1.0",
    "App-s2p",
    "APSL-1.0",
    "APSL-1.1",
    "APSL-1.2",
    "APSL-2.0",
    "Arphic-1999",
    "Artistic-1.0",
    "Artistic-1.0-cl8",
    "Artistic-1.0-Perl",
    "Artistic-2.0",
    "ASWF-Digital-Assets-1.0",
    "ASWF-Digital-Assets-1.1",
    "Baekmuk",
    "Bahyph",
    "Barr",
    "bcrypt-Solar-Designer",
    "Beerware",
    "Bitstream-Charter",
    "Bitstream-Vera",
    "BitTorrent-1.0",
    "BitTorrent-1.1",
    "blessing",
    "BlueOak-1.0.0",
    "Boehm-GC",
    "Boehm-GC-without-fee",
    "Borceux",
    "Brian-Gladman-2-Clause",
    "Brian-Gladman-3-Clause",
    "BSD-1-Clause",
    "BSD-2-Clause",
    "BSD-2-Clause-Darwin",
    "BSD-2-Clause-first-lines",
    "BSD-2-Clause-Patent",
    "BSD-2-Clause-Views",
    "BSD-3-Clause",
    "BSD-3-Clause-acpica",
    "BSD-3-Clause-Attribution",
    "BSD-3-Clause-Clear",
    "BSD-3-Clause-flex",
    "BSD-3-Clause-HP",
    "BSD-3-Clause-LBNL",
    "BSD-3-Clause-Modification",
    "BSD-3-Clause-No-Military-License",
    "BSD-3-Clause-No-Nuclear-License",
    "BSD-3-Clause-No-Nuclear-License-2014",
    "BSD-3-Clause-No-Nuclear-Warranty",
    "BSD-3-Clause-Open-MPI",
    "BSD-3-Clause-Sun",
    "BSD-4.3RENO",
    "BSD-4.3TAHOE",
    "BSD-4-Clause",
    "BSD-4-Clause-Shortened",
    "BSD-4-Clause-UC",
    "BSD-Advertising-Acknowledgement",
    "BSD-Attribution-HPND-disclaimer",
    "BSD-Inferno-Nettverk",
    "BSD-Protection",
    "BSD-Source-beginning-file",
    "BSD-Source-Code",
    "BSD-Systemics",
    "BSD-Systemics-W3Works",
    "BSL-1.0",
    "BUSL-1.1",
    "bzip2-1.0.6",
    "CAL-1.0",
    "CAL-1.0-Combined-Work-Exception",
    "Caldera",
    "Caldera-no-preamble",
    "Catharon",
    "CATOSL-1.1",
    "CC0-1.0",
    "CC-BY-1.0",
    "CC-BY-2.0",
    "CC-BY-2.5",
    "CC-BY-2.5-AU",
    "CC-BY-3.0",
    "CC-BY-3.0-AT",
    "CC-BY-3.0-AU",
    "CC-BY-3.0-DE",
    "CC-BY-3.0-IGO",
    "CC-BY-3.0-NL",
    "CC-BY-3.0-US",
    "CC-BY-4.0",
    "CC-BY-NC-1.0",
    "CC-BY-NC-2.0",
    "CC-BY-NC-2.5",
    "CC-BY-NC-3.0",
    "CC-BY-NC-3.0-DE",
    "CC-BY-NC-4.0",
    "CC-BY-NC-ND-1.0",
    "CC-BY-NC-ND-2.0",
    "CC-BY-NC-ND-2.5",
    "CC-BY-NC-ND-3.0",
    "CC-BY-NC-ND-3.0-DE",
    "CC-BY-NC-ND-3.0-IGO",
    "CC-BY-NC-ND-4.0",
    "CC-BY-NC-SA-1.0",
    "CC-BY-NC-SA-2.0",
    "CC-BY-NC-SA-2.0-DE",
    "CC-BY-NC-SA-2.0-FR",
    "CC-BY-NC-SA-2.0-UK",
    "CC-BY-NC-SA-2.5",
    "CC-BY-NC-SA-3.0",
    "CC-BY-NC-SA-3.0-DE",
    "CC-BY-NC-SA-3.0-IGO",
    "CC-BY-NC-SA-4.0",
    "CC-BY-ND-1.0",
    "CC-BY-ND-2.0",
    "CC-BY-ND-2.5",
    "CC-BY-ND-3.0",
    "CC-BY-ND-3.0-DE",
    "CC-BY-ND-4.0",
    "CC-BY-SA-1.0",
    "CC-BY-SA-2.0",
    "CC-BY-SA-2.0-UK",
    "CC-BY-SA-2.1-JP",
    "CC-BY-SA-2.5",
    "CC-BY-SA-3.0",
    "CC-BY-SA-3.0-AT",
    "CC-BY-SA-3.0-DE",
    "CC-BY-SA-3.0-IGO",
    "CC-BY-SA-4.0",
    "CC-PDDC",
    "CC-PDM-1.0",
    "CC-SA-1.0",
    "CDDL-1.0",
    "CDDL-1.1",
    "CDL-1.0",
    "CDLA-Permissive-1.0",
    "CDLA-Permissive-2.0",
    "CDLA-Sharing-1.0",
    "CECILL-1.0",
    "CECILL-1.1",
    "CECILL-2.0",
    "CECILL-2.1",
    "CECILL-B",
    "CECILL-C",
    "CERN-OHL-1.1",
    "CERN-OHL-1.2",
    "CERN-OHL-P-2.0",
    "CERN-OHL-S-2.0",
    "CERN-OHL-W-2.0",
    "CFITSIO",
    "check-cvs",
    "checkmk",
    "ClArtistic",
    "Clips",
    "CMU-Mach",
    "CMU-Mach-nodoc",
    "CNRI-Jython",
    "CNRI-Python",
    "CNRI-Python-GPL-Compatible",
    "COIL-1.0",
    "Community-Spec-1.0",
    "Condor-1.1",
    "copyleft-next-0.3.0",
    "copyleft-next-0.3.1",
    "Cornell-Lossless-JPEG",
    "CPAL-1.0",
    "CPL-1.0",
    "CPOL-1.02",
    "Cronyx",
    "Crossword",
    "CrystalStacker",
    "CUA-OPL-1.0",
    "Cube",
    "C-UDA-1.0",
    "curl",
    "cve-tou",
    "DEC-3-Clause",
    "D-FSL-1.0",
    "diffmark",
    "DL-DE-BY-2.0",
    "DL-DE-ZERO-2.0",
    "DOC",
    "DocBook-Schema",
    "DocBook-Stylesheet",
    "DocBook-XML",
    "Dotseqn",
    "DRL-1.0",
    "DRL-1.1",
    "DSDP",
    "dtoa",
    "dvipdfm",
    "ECL-1.0",
    "ECL-2.0",
    "EFL-1.0",
    "EFL-2.0",
    "eGenix",
    "Elastic-2.0",
    "Entessa",
    "EPICS",
    "EPL-1.0",
    "EPL-2.0",
    "ErlPL-1.1",
    "etalab-2.0",
    "EUDatagrid",
    "EUPL-1.0",
    "EUPL-1.1",
    "EUPL-1.2",
    "Eurosym",
    "Fair",
    "FBM",
    "FDK-AAC",
    "Ferguson-Twofish",
    "Frameworx-1.0",
    "FreeBSD-DOC",
    "FreeImage",
    "FSFAP",
    "FSFAP-no-warranty-disclaimer",
    "FSFUL",
    "FSFULLR",
    "FSFULLRWD",
    "FTL",
    "Furuseth",
    "fwlw",
    "GCR-docs",
    "GD",
    "generic-xts",
    "GFDL-1.1-invariants-only",
    "GFDL-1.1-invariants-or-later",
    "GFDL-1.1-no-invariants-only",
    "GFDL-1.1-no-invariants-or-later",
    "GFDL-1.1-only",
    "GFDL-1.1-or-later",
    "GFDL-1.2-invariants-only",
    "GFDL-1.2-invariants-or-later",
    "GFDL-1.2-no-invariants-only",
    "GFDL-1.2-no-invariants-or-later",
    "GFDL-1.2-only",
    "GFDL-1.2-or-later",
    "GFDL-1.3-invariants-only",
    "GFDL-1.3-invariants-or-later",
    "GFDL-1.3-no-invariants-only",
    "GFDL-1.3-no-invariants-or-later",
    "GFDL-1.3-only",
    "GFDL-1.3-or-later",
    "Giftware",
    "GL2PS",
    "Glide",
    "Glulxe",
    "GLWTPL",
    "gnuplot",
    "GPL-1.0-only",
    "GPL-1.0-or-later",
    "GPL-2.0-only",
    "GPL-2.0-or-later",
    "GPL-3.0-only",
    "GPL-3.0-or-later",
    "Graphics-Gems",
    "gSOAP-1.3b",
    "gtkbook",
    "Gutmann",
    "HaskellReport",
    "hdparm",
    "HIDAPI",
    "Hippocratic-2.1",
    "HP-1986",
    "HP-1989",
    "HPND",
    "HPND-DEC",
    "HPND-doc",
    "HPND-doc-sell",
    "HPND-export2-US",
    "HPND-export-US",
    "HPND-export-US-acknowledgement",
    "HPND-export-US-modify",
    "HPND-Fenneberg-Livingston",
    "HPND-INRIA-IMAG",
    "HPND-Intel",
    "HPND-Kevlin-Henney",
    "HPND-Markus-Kuhn",
    "HPND-merchantability-variant",
    "HPND-MIT-disclaimer",
    "HPND-Netrek",
    "HPND-Pbmplus",
    "HPND-sell-MIT-disclaimer-xserver",
    "HPND-sell-regexpr",
    "HPND-sell-variant",
    "HPND-sell-variant-MIT-disclaimer",
    "HPND-sell-variant-MIT-disclaimer-rev",
    "HPND-UC",
    "HPND-UC-export-US",
    "HTMLTIDY",
    "IBM-pibs",
    "ICU",
    "IEC-Code-Components-EULA",
    "IJG",
    "IJG-short",
    "ImageMagick",
    "iMatix",
    "Imlib2",
    "Info-ZIP",
    "Inner-Net-2.0",
    "InnoSetup",
    "Intel",
    "Intel-ACPI",
    "Interbase-1.0",
    "IPA",
    "IPL-1.0",
    "ISC",
    "ISC-Veillard",
    "Jam",
    "JasPer-2.0",
    "JPL-image",
    "JPNIC",
    "JSON",
    "Kastrup",
    "Kazlib",
    "Knuth-CTAN",
    "LAL-1.2",
    "LAL-1.3",
    "Latex2e",
    "Latex2e-translated-notice",
    "Leptonica",
    "LGPL-2.0-only",
    "LGPL-2.0-or-later",
    "LGPL-2.1-only",
    "LGPL-2.1-or-later",
    "LGPL-3.0-only",
    "LGPL-3.0-or-later",
    "LGPLLR",
    "Libpng",
    "libpng-2.0",
    "libselinux-1.0",
    "libtiff",
    "libutil-David-Nugent",
    "LiLiQ-P-1.1",
    "LiLiQ-R-1.1",
    "LiLiQ-Rplus-1.1",
    "Linux-man-pages-1-para",
    "Linux-man-pages-copyleft",
    "Linux-man-pages-copyleft-2-para",
    "Linux-man-pages-copyleft-var",
    "Linux-OpenIB",
    "LOOP",
    "LPD-document",
    "LPL-1.0",
    "LPL-1.02",
    "LPPL-1.0",
    "LPPL-1.1",
    "LPPL-1.2",
    "LPPL-1.3a",
    "LPPL-1.3c",
    "lsof",
    "Lucida-Bitmap-Fonts",
    "LZMA-SDK-9.11-to-9.20",
    "LZMA-SDK-9.22",
    "Mackerras-3-Clause",
    "Mackerras-3-Clause-acknowledgment",
    "magaz",
    "mailprio",
    "MakeIndex",
    "Martin-Birgmeier",
    "McPhee-slideshow",
    "metamail",
    "Minpack",
    "MIPS",
    "MirOS",
    "MIT",
    "MIT-0",
    "MIT-advertising",
    "MIT-Click",
    "MIT-CMU",
    "MIT-enna",
    "MIT-feh",
    "MIT-Festival",
    "MIT-Khronos-old",
    "MIT-Modern-Variant",
    "MITNFA",
    "MIT-open-group",
    "MIT-testregex",
    "MIT-Wu",
    "MMIXware",
    "Motosoto",
    "MPEG-SSG",
    "mpich2",
    "mpi-permissive",
    "MPL-1.0",
    "MPL-1.1",
    "MPL-2.0",
    "MPL-2.0-no-copyleft-exception",
    "mplus",
    "MS-LPL",
    "MS-PL",
    "MS-RL",
    "MTLL",
    "MulanPSL-1.0",
    "MulanPSL-2.0",
    "Multics",
    "Mup",
    "NAIST-2003",
    "NASA-1.3",
    "Naumen",
    "NBPL-1.0",
    "NCBI-PD",
    "NCGL-UK-2.0",
    "NCL",
    "NCSA",
    "NetCDF",
    "Newsletr",
    "NGPL",
    "NICTA-1.0",
    "NIST-PD",
    "NIST-PD-fallback",
    "NIST-Software",
    "NLOD-1.0",
    "NLOD-2.0",
    "NLPL",
    "Nokia",
    "NOSL",
    "Noweb",
    "NPL-1.0",
    "NPL-1.1",
    "NPOSL-3.0",
    "NRL",
    "NTP",
    "NTP-0",
    "OAR",
    "OCCT-PL",
    "OCLC-2.0",
    "ODbL-1.0",
    "ODC-By-1.0",
    "OFFIS",
    "OFL-1.0",
    "OFL-1.0-no-RFN",
    "OFL-1.0-RFN",
    "OFL-1.1",
    "OFL-1.1-no-RFN",
    "OFL-1.1-RFN",
    "OGC-1.0",
    "OGDL-Taiwan-1.0",
    "OGL-Canada-2.0",
    "OGL-UK-1.0",
    "OGL-UK-2.0",
    "OGL-UK-3.0",
    "OGTSL",
    "OLDAP-1.1",
    "OLDAP-1.2",
    "OLDAP-1.3",
    "OLDAP-1.4",
    "OLDAP-2.0",
    "OLDAP-2.0.1",
    "OLDAP-2.1",
    "OLDAP-2.2",
    "OLDAP-2.2.1",
    "OLDAP-2.2.2",
    "OLDAP-2.3",
    "OLDAP-2.4",
    "OLDAP-2.5",
    "OLDAP-2.6",
    "OLDAP-2.7",
    "OLDAP-2.8",
    "OLFL-1.3",
    "OML",
    "OpenPBS-2.3",
    "OpenSSL",
    "OpenSSL-standalone",
    "OpenVision",
    "OPL-1.0",
    "OPL-UK-3.0",
    "OPUBL-1.0",
    "OSET-PL-2.1",
    "OSL-1.0",
    "OSL-1.1",
    "OSL-2.0",
    "OSL-2.1",
    "OSL-3.0",
    "O-UDA-1.0",
    "PADL",
    "Parity-6.0.0",
    "Parity-7.0.0",
    "PDDL-1.0",
    "PHP-3.0",
    "PHP-3.01",
    "Pixar",
    "pkgconf",
    "Plexus",
    "pnmstitch",
    "PolyForm-Noncommercial-1.0.0",
    "PolyForm-Small-Business-1.0.0",
    "PostgreSQL",
    "PPL",
    "PSF-2.0",
    "psfrag",
    "psutils",
    "Python-2.0",
    "Python-2.0.1",
    "python-ldap",
    "Qhull",
    "QPL-1.0",
    "QPL-1.0-INRIA-2004",
    "radvd",
    "Rdisc",
    "RHeCos-1.1",
    "RPL-1.1",
    "RPL-1.5",
    "RPSL-1.0",
    "RSA-MD",
    "RSCPL",
    "Ruby",
    "Ruby-pty",
    "Saxpath",
    "SAX-PD",
    "SAX-PD-2.0",
    "SCEA",
    "SchemeReport",
    "Sendmail",
    "Sendmail-8.23",
    "Sendmail-Open-Source-1.1",
    "SGI-B-1.0",
    "SGI-B-1.1",
    "SGI-B-2.0",
    "SGI-OpenGL",
    "SGP4",
    "SHL-0.5",
    "SHL-0.51",
    "SimPL-2.0",
    "SISSL",
    "SISSL-1.2",
    "SL",
    "Sleepycat",
    "SMAIL-GPL",
    "SMLNJ",
    "SMPPL",
    "SNIA",
    "snprintf",
    "softSurfer",
    "Soundex",
    "Spencer-86",
    "Spencer-94",
    "Spencer-99",
    "SPL-1.0",
    "ssh-keyscan",
    "SSH-OpenSSH",
    "SSH-short",
    "SSLeay-standalone",
    "SSPL-1.0",
    "SugarCRM-1.1.3",
    "Sun-PPP",
    "Sun-PPP-2000",
    "SunPro",
    "SWL",
    "swrule",
    "Symlinks",
    "TAPR-OHL-1.0",
    "TCL",
    "TCP-wrappers",
    "TermReadKey",
    "TGPPL-1.0",
    "ThirdEye",
    "threeparttable",
    "TMate",
    "TORQUE-1.1",
    "TOSL",
    "TPDL",
    "TPL-1.0",
    "TrustedQSL",
    "TTWL",
    "TTYP0",
    "TU-Berlin-1.0",
    "TU-Berlin-2.0",
    "Ubuntu-font-1.0",
    "UCAR",
    "UCL-1.0",
    "ulem",
    "UMich-Merit",
    "Unicode-3.0",
    "Unicode-DFS-2015",
    "Unicode-DFS-2016",
    "Unicode-TOU",
    "UnixCrypt",
    "Unlicense",
    "UPL-1.0",
    "URT-RLE",
    "Vim",
    "VOSTROM",
    "VSL-1.0",
    "W3C",
    "W3C-19980720",
    "W3C-20150513",
    "w3m",
    "Watcom-1.0",
    "Widget-Workshop",
    "Wsuipa",
    "WTFPL",
    "wwl",
    "X11",
    "X11-distribute-modifications-variant",
    "X11-swapped",
    "Xdebug-1.03",
    "Xerox",
    "Xfig",
    "XFree86-1.1",
    "xinetd",
    "xkeyboard-config-Zinoviev",
    "xlock",
    "Xnet",
    "xpp",
    "XSkat",
    "xzoom",
    "YPL-1.0",
    "YPL-1.1",
    "Zed",
    "Zeeff",
    "Zend-2.0",
    "Zimbra-1.3",
    "Zimbra-1.4",
    "Zlib",
    "zlib-acknowledgement",
    "ZPL-1.1",
    "ZPL-2.0",
    "ZPL-2.1",
];
